/** @addtogroup MCD_MCDIMPL_DAEMON_SRV
 * @{
 * @file
 *
 * Connection server.
 *
 * Handles incoming socket connections from clients using the MobiCore driver.
 *
 * <!-- Copyright Giesecke & Devrient GmbH 2009 - 2012 -->
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "public/NetlinkServer.h"
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <linux/netlink.h>

#include <stdlib.h>
#include "NetlinkConnection.h"
#include <signal.h>

#define LOG_TAG	"McDaemon"
#include "log.h"

//------------------------------------------------------------------------------
NetlinkServer::NetlinkServer(
    ConnectionHandler * connectionHandler
):Server(connectionHandler, "dummy") {
}


//------------------------------------------------------------------------------
void NetlinkServer::run(
) {
	do
	{
		LOG_I("%s: start listening on netlink bus", __func__);
		
		// Open a socket
		serverSock = socket(PF_NETLINK, SOCK_DGRAM,  MC_DAEMON_NETLINK);
		if (serverSock < 0)
		{
			LOG_E("run(): can't open socket, errno=%d", errno);
			break;
		}

		// Fill in address structure and bind to socket
		struct sockaddr_nl src_addr;
		struct nlmsghdr *nlh = NULL;
		struct iovec iov;
		struct msghdr msg;
		uint32_t len;
		
		memset(&src_addr, 0, sizeof(src_addr));
		src_addr.nl_family = AF_NETLINK;
		src_addr.nl_pid = MC_DAEMON_PID;  /* daemon pid */
		src_addr.nl_groups = 0;  /* not in mcast groups */
		if(bind(serverSock, (struct sockaddr*)&src_addr, sizeof(src_addr)) < 0){
			LOG_E("bind() to server socket failed, errno=%d(%s)", 
					errno, strerror(errno));
			close(serverSock);
			break;
		}

		// Start reading the socket
		LOG_I("\n********* successfully initialized *********\n");

		for (;;)
		{
			// This buffer will be taken over by the connection it was routed to
			nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
			memset(&msg, 0, sizeof(msg));
			iov.iov_base = (void *)nlh;
			iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
			msg.msg_iov = &iov;
			msg.msg_iovlen = 1;
			msg.msg_name = &src_addr;
			msg.msg_namelen = sizeof(src_addr);
			
			memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
			
			// Read the incomming message and route it to the connection based
			// on the incomming PID
			if ((len = recvmsg(serverSock, &msg, 0)) < 0) {
				LOG_E("%s: recvmsg() failed, errno=%d(%s)", 
						__func__, errno, strerror(errno));
				break;
			}
			
            if (NLMSG_OK(nlh, len)) {
				handleMessage(nlh);
			} else {
				break;
			}
		}
	} while(false);

    LOG_E("%s: exiting due to error, errno=%d(%s)",
		  __func__, errno, strerror(errno));
}

//------------------------------------------------------------------------------
void NetlinkServer::handleMessage(
    struct nlmsghdr *nlh
) {
	uint32_t seq = nlh->nlmsg_seq;
	uint32_t pid = nlh->nlmsg_pid;
	//LOG_I("%s: Handling NQ message for pid %u seq %u...", __func__, pid, seq);
	uint64_t hash = hashConnection(pid, seq);
	/* First cleanup the connection list */
	cleanupConnections();

	NetlinkConnection *connection = findConnection(hash);
	// This is a message from a new client
	if (connection == NULL) {
		//LOG_I("%s: Cound't find the connection, creating a new one", __func__);
		connection = new NetlinkConnection(this, serverSock, pid, seq);
		// Add the new connection
		insertConnection(hash, connection);
	}

	connection->handleMessage(nlh);

	// Only handle connections which have not been detached
	if(connection->detached == false)
	{
		if (!connectionHandler->handleConnection(connection))
		{
			LOG_I("%s: No command processed.", __func__);
			connection->socketDescriptor = -1;
			//Inform the driver
			connectionHandler->dropConnection(connection);
			
			// Remove connection from list
			removeConnection(hash);
			connection->socketDescriptor = -1;
			delete connection;
		}
		// If connection data is set to NULL then device close has been called 
		// so we must remove all connections associated with this hash
		else if (connection->connectionData == NULL && 
			connection->detached == false)
		{
			delete connection;
		}
	}
}


//------------------------------------------------------------------------------
void NetlinkServer::detachConnection(
    Connection *connection
) {
	connection->detached = true;
}


//------------------------------------------------------------------------------
NetlinkServer::~NetlinkServer(
	void
) {
	connectionMap_t::iterator i;
	// Shut down the server socket
	close(serverSock);

	// Destroy all client connections
	for (i = peerConnections.begin(); i != peerConnections.end(); i++) {
		if (i->second->detached == false) {
			delete i->second;
		}
	}
	peerConnections.clear();
}


//------------------------------------------------------------------------------
NetlinkConnection* NetlinkServer::findConnection(
    uint64_t hash
) {
	connectionMap_t::iterator i = peerConnections.find(hash);
	if (i != peerConnections.end()) {
		return i->second;
	}

	return NULL;
}


//------------------------------------------------------------------------------
void NetlinkServer::insertConnection(
	uint64_t hash,
    NetlinkConnection *connection
) {
	peerConnections[hash] = connection;
}

/* This is called from multiple threads! */
//------------------------------------------------------------------------------
void NetlinkServer::removeConnection(
	uint64_t hash
) {
	connectionMap_t::iterator i = peerConnections.find(hash);
	if (i != peerConnections.end()){
		peerConnections.erase(i);
	}
}

//------------------------------------------------------------------------------
void NetlinkServer::cleanupConnections(
	void
) {
	connectionMap_t::reverse_iterator i;
	pid_t pid;
	NetlinkConnection *connection = NULL;
	// Destroy all client connections
	for (i = peerConnections.rbegin(); i != peerConnections.rend(); ++i) {
		connection = i->second;
		// Only 16 bits are for the actual PID, the rest is session magic
		pid = connection->peerPid & 0xFFFF;
		//LOG_I("%s: checking PID %u", __func__, pid);
		// Check if the peer pid is still alive
		if (pid == 0) {
			continue;
		}
		if (kill(pid, 0)) {
			bool detached = connection->detached;
			LOG_I("%s: PID %u has died, cleaning up session 0x%X", 
				  __func__, pid, connection->peerPid);
			
			connection->socketDescriptor = -1;
			//Inform the driver
			connectionHandler->dropConnection(connection);
			
			// We aren't handling this connection anymore no matter what
			removeConnection(connection->hash);
			
			// Remove connection from list only if detached, the detached
			// connections are managed by the device
			if (detached == false) {
				delete connection;
			}
			if (peerConnections.size() == 0) {
				break;
			}
			i = peerConnections.rbegin();
		}
	}
}

/** @} */
